package br.edu.ufcg.unknown.sintatico;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import br.edu.ufcg.unknown.exception.AnalisadorSemanticoUnknownRuntimeException;
import br.edu.ufcg.unknown.exception.AnalisadorSintaticoUnknownRuntimeException;
import br.edu.ufcg.unknown.exception.UnknownException;
import br.edu.ufcg.unknown.geradorDeCodigo.GeradorDeCodigo;
import br.edu.ufcg.unknown.lexico.AnalisadorLexico;
import br.edu.ufcg.unknown.lexico.Token;
import br.edu.ufcg.unknown.semantico.AnalisadorSemantico;
import br.edu.ufcg.unknown.semantico.Atributo;
import br.edu.ufcg.unknown.semantico.Tipo;
import br.edu.ufcg.unknown.semantico.arvore.Nodo;

/**
 * Analisador Sintático. Responsável por ler um fluxo de tokens de entrada e e verificar se o fluxo
 * é reconhecido pela gramática.
 * 
 * @author Fábio Jorge
 * @author Jaindson Valentim
 * @author Moisés Rodrigues
 */
public class AnalisadorSintatico {

	/**
	 * Entidade que faz o casamento de tokens com os padrões definidos na gramática.
	 */
	private IdentificaTKAnalisadorSintatico ident;

	/**
	 * O analisador léxico utilizado por esse analisador sintático.
	 */
	private AnalisadorLexico lexico;

	/**
	 * O token sendo analisado no momento.
	 */
	private Token tk;

	private Token tkPenultimo;

	/**
	 * Pilha de lexemas esperados pelo analisador sintático.
	 */
	private Stack<String> lexemasEsperados;

	private List<AnalisadorSintaticoUnknownRuntimeException> listaDeErros = new ArrayList<AnalisadorSintaticoUnknownRuntimeException>();

	/**
	 * O tipo sendo analizado no momento
	 */
	private Tipo tipo;

	/**
	 * Entidade que faz a analize semântica
	 */
	private AnalisadorSemantico aSemantico;

	private GeradorDeCodigo geradorDeCodigo;

	/**
	 * True se por acaso o identificador utilizado na atribuição é um array.
	 */
	private boolean identificadorAtribuicaoEhArray;

	/**
	 * Cria um analisador sintático recebendo o File com o código fonte e as palavras reservadas.
	 * 
	 * @param codigoFonte
	 *            arquivo com o codigo fonte a ser analisado
	 * @param palavrasReservadas
	 *            palavras reservadas da linguagem desse analisador
	 * @throws UnknownException
	 */
	public AnalisadorSintatico(File codigoFonte, File palavrasReservadas) throws UnknownException {
		this.lexico = new AnalisadorLexico(codigoFonte, palavrasReservadas);
		init();
	}

	/**
	 * Cria um analisador sintático recebendo o caminho para o código fonte e o File das palavras
	 * reservadas.
	 * 
	 * @param codigoFonte
	 *            caminho para o arquivo do código fonte a ser analisado
	 * @param palavrasReservadas
	 *            palavras reservadas da linguagem desse analisador
	 * @throws UnknownException
	 */
	public AnalisadorSintatico(String codigoFonte, File palavrasReservadas) throws UnknownException {
		this.lexico = new AnalisadorLexico(codigoFonte, palavrasReservadas);
		init();
	}

	/**
	 * Inicializa os parâmetros do analisador sintático.
	 * 
	 * @param palavrasReservadas
	 *            palavras reservadas da linguagem desse analisador
	 * @throws UnknownException
	 */
	private void init() throws UnknownException {
		this.tk = null;
		this.lexemasEsperados = new Stack<String>();
		this.ident = new IdentificaTKAnalisadorSintatico(this);
		this.aSemantico = new AnalisadorSemantico();
		this.tipo = null;
		this.geradorDeCodigo = GeradorDeCodigo.getInstance();
	}

	/**
	 * Atualiza a váriavel tk com o token corrente a ser analisado.
	 * 
	 */
	protected void obtemToken() {
		this.tkPenultimo = this.tk;
		this.tk = lexico.getProxToken();

	}

	/**
	 * Limpa a pilha de lexemas esperados.
	 * 
	 */
	protected void esvaziarLexemasEsperados() {
		this.lexemasEsperados.clear();
	}

	/**
	 * Retorna um iterator para os lexemas esperados pelo analisador.
	 * 
	 * @return um iterator para os lexemas esperados pelo analisador
	 */
	protected Iterator<String> getIteratorLexemasEsperados() {
		return this.lexemasEsperados.iterator();
	}

	/**
	 * Adiciona um lexema esperado na pilha de lexemas esperados pelo analisador.
	 * 
	 * @param lexema
	 *            o lexema a ser adicionado
	 */
	protected void addLexemaEsperado(String lexema) {
		this.lexemasEsperados.add(lexema);
	}

	/**
	 * Lanca um erro detectado pelo analisador recebendo o token corrente e o símbolo esperado.
	 * 
	 * @param tk
	 *            token corrente
	 * @param simboloTerminalOuNaoTerminalEsperado
	 *            símbolo esperado pelo analisador
	 */
	protected void lancarErro(Token tk, String simboloTerminalOuNaoTerminalEsperado) {

		addErro(tk, simboloTerminalOuNaoTerminalEsperado);

		throw getErro();
	}

	protected void addErro(Token tk, String simboloTerminalOuNaoTerminalEsperado,
			List<String> lexemasEsperados) {
		for (String lexema : lexemasEsperados) {
			this.addLexemaEsperado(lexema);
		}

		addErro(tk, simboloTerminalOuNaoTerminalEsperado);
	}

	protected void addErro(Token tk, String simboloTerminalOuNaoTerminalEsperado) {
		this.listaDeErros.add(getErro(tk, simboloTerminalOuNaoTerminalEsperado));
	}

	public AnalisadorSintaticoUnknownRuntimeException getErro() {
		AnalisadorSintaticoUnknownRuntimeException e = null;

		if (this.listaDeErros.size() == 0)
			return null;

		if (this.listaDeErros.size() == 1)
			return this.listaDeErros.get(0);

		e = this.listaDeErros.get(0);

		for (int i = 1; i < this.listaDeErros.size(); i++) {
			e.addErro(this.listaDeErros.get(i));
		}

		return e;
	}

	protected AnalisadorSintaticoUnknownRuntimeException getErro(Token tk,
			String simboloTerminalOuNaoTerminalEsperado) {
		AnalisadorSintaticoUnknownRuntimeException e = new AnalisadorSintaticoUnknownRuntimeException(
				tk != null ? tk.getLexema() : "<Fim De Arquivo>", lexico.getLinha(), lexico
						.getColuna(), simboloTerminalOuNaoTerminalEsperado);
		Iterator<String> iter = getIteratorLexemasEsperados();
		while (iter.hasNext()) {
			e.addLexemaEsperado(iter.next());
		}
		esvaziarLexemasEsperados();

		return e;
	}

	/**
	 * Valida o código fonte a ser analisado por esse analisador.
	 * 
	 * @return true se o código for válido, caso contrário false
	 */
	public boolean validar() {
		if (!programa()) {
			lancarErro(tk, "<programa>");
			return false;
		}

		return true;
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <programa_> ::= PROGRAM IDENTIFICADOR
	 * <secao_declaracao_constantes> <secao_declaracao_variaveis><secao_subprograma><bloco_>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean programa() {
		if (!this.ident.eh_PROGRAM(tk)) {
			return false;
		}

		if (!this.ident.eh_IDENTIFICADOR(tk)) {
			lancarErro(tk, "<t_ident>");
			return false;
		}

		if (!secaoDeclaracaoConstantes()) {
			lancarErro(tk, "<secao_declaracao_constantes>");
			return false;
		}
		if (!secaoDeclaracaoVariaveis()) {
			lancarErro(tk, "<secao_declaracao_variaveis>");
			return false;
		}
		if (!secaoSuprograma()) {
			lancarErro(tk, "<secao_suprograma>");
			return false;
		}

		if (!bloco()) {
			lancarErro(tk, "<bloco>");
			return false;
		}

		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <valor_tipo> ::= INTEGER | BOOLEAN |
	 * STRING
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean valorTipo() {

		this.tipo = new Tipo(this.tk, false);
		if (!this.ident.eh_INTEGER(tk) && !this.ident.eh_BOOL(tk) && !this.ident.eh_STRING(tk)) {
			return false;
		}
		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <valor_tipo_constante> ::= TRUE | FALSE |
	 * ALFA | NUM
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */

	protected boolean valorTipoConstante() {

		// Tipo atual a declarado na tabela de Simbolos
		this.tipo = new Tipo(this.tk);

		if (!this.ident.eh_TRUE(tk) && !this.ident.eh_FALSE(tk) && !this.ident.eh_ALFA(tk)
				&& !this.ident.eh_NUM(tk)) {
			return false;
		}
		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <tipo_> ::= <valor_tipo> | ARRAY [ NUM ]
	 * OF <valor_tipo>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean tipo() {

		if (this.ident.eh_ARRAY(tk)) {

			if (!this.ident.eh_ABRE_COLCHETES(tk)) {
				lancarErro(tk, "<t_abre_colc>");
				return false;
			}

			if (!this.ident.eh_NUM(tk)) {
				lancarErro(tk, "<t_numero>");
				return false;
			}

			// <tratamento semântico do tamanho do array>
			// "Linha: 1 - Coluna: 1 - Tamanho de array inválido: "
			Integer tamanho = null;
			try {
				tamanho = Integer.parseInt(this.tkPenultimo.getLexema());
				if (tamanho != null && (tamanho <= 0 || tamanho > Tipo.TAM_MAXIMO_INTEIRO)) {
					throw new AnalisadorSemanticoUnknownRuntimeException("Linha: "
							+ this.tkPenultimo.getLinha() + " - Coluna: "
							+ this.tkPenultimo.getColuna() + " - Tamanho de array inválido: "
							+ tamanho);
				}
			} catch (NumberFormatException e) {
				throw new AnalisadorSemanticoUnknownRuntimeException("Linha: "
						+ this.tkPenultimo.getLinha() + " - Coluna: "
						+ this.tkPenultimo.getColuna() + " - Tamanho de array inválido: "
						+ this.tkPenultimo.getLexema());
			}
			// </tratamento semântico do tamanho do array>

			if (!this.ident.eh_FECHA_COLCHETES(tk)) {
				lancarErro(tk, "<t_fecha_colc>");
				return false;
			}

			if (!this.ident.eh_OF(tk)) {
				lancarErro(tk, "<t_of>");
				return false;
			}

			if (!valorTipo()) {
				lancarErro(tk, "<valor_tipo>");
				return false;
			}

			// modifica o estado do tipo global caso seja um tipo array.
			this.tipo.setEhArray(true);

			return true;
		}

		if (!valorTipo()) {
			return false;
		}

		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <declaracao_constante> ::=
	 * IDENTIFICADOR = <valor_tipo_constante>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */

	protected boolean declaracaoConstante() {

		// armazena o token da constante a ser declarada
		Token identificador = this.tk;

		if (!this.ident.eh_IDENTIFICADOR(tk)) {
			return false;
		}

		if (!this.ident.eh_IGUAL(tk)) {
			lancarErro(tk, "<t_igual>");
			return false;
		}

		if (!valorTipoConstante()) {
			lancarErro(tk, "<valor_tipo_constante>");
			return false;
		}

		// add na tabela de simbolos o atributo identificado = constante
		// declarada + tipo
		this.aSemantico.addSimbolo(identificador, this.tipo);

		return true;
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <lista_declaracao_constante> ::=
	 * <declaracao_constante> ; <lista_declaracao_constante> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean listaDeclaracaoConstante() {
		if (declaracaoConstante()) {
			if (!this.ident.eh_PONTO_VIRGULA(tk)) {
				lancarErro(tk, "<t_pto_vgl>");
				return false;
			}

			if (!listaDeclaracaoConstante()) {
				lancarErro(tk, "<lista_declaracao_constante>");
				return false;
			}

			return true;
		}
		return this.ident.eh_LAMBDA();

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <secao_declaracao_constantes> ::= CONST
	 * <lista_declaracao_constante> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */

	protected boolean secaoDeclaracaoConstantes() {

		if (this.ident.eh_CONST(tk)) {

			if (!listaDeclaracaoConstante()) {
				lancarErro(tk, "<lista_declaracao_constante>");
				return false;
			}

			return true;
		}
		return this.ident.eh_LAMBDA();

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <declaracao_variavel> ::= IDENTIFICADOR :
	 * <tipo_>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean declaracaoVariavel() {

		// armazena o token da variavel a ser declarada
		Token identificador = this.tk;

		if (!this.ident.eh_IDENTIFICADOR(tk)) {
			return false;
		}

		if (!this.ident.eh_DOIS_PONTOS(tk)) {
			lancarErro(tk, "<t_dois_pts>");
			return false;
		}

		if (!tipo()) {
			lancarErro(tk, "<tipo>");
			return false;
		}

		// add na tabela de simbolos o atributo identificado = varialvel
		// declarada + tipo
		this.aSemantico.addSimbolo(identificador, this.tipo);
		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <lista_declaracao_variavel> ::=
	 * <declaracao_veriavel> ; <lista_declaracao_variavel> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */

	protected boolean listaDeclaracaoVariavel() {

		if (declaracaoVariavel()) {

			if (!this.ident.eh_PONTO_VIRGULA(tk)) {
				lancarErro(tk, "<t_pto_vgl>");
				return false;
			}

			if (!listaDeclaracaoVariavel()) {
				lancarErro(tk, "<lista_declaracao_variavel>");
				return false;
			}

			return true;
		}
		return this.ident.eh_LAMBDA();

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <secao_declaracao_variaveis> ::= VAR
	 * <lista_declaracao_variavel> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean secaoDeclaracaoVariaveis() {
		if (this.ident.eh_VAR(tk)) {

			if (!listaDeclaracaoVariavel()) {
				lancarErro(tk, "<lista_declaracao_variavel>");
				return false;
			}

			return true;
		}
		return this.ident.eh_LAMBDA();
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <subprograma_> ::= SUBPROGRAM
	 * IDENTIFICADOR ( ) ; <bloco>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean subprograma() {

		if (!this.ident.eh_SUBPROGRAM(tk)) {
			return false;
		}
		Token tkAtual = tk;
		if (!this.ident.eh_IDENTIFICADOR(tk)) {
			lancarErro(tk, "<t_ident>");
			return false;
		}
		this.aSemantico.addSimbolo(tkAtual, null);

		if (!this.ident.eh_ABRE_PARENTESES(tk)) {
			lancarErro(tk, "<t_abre_par>");
			return false;
		}
		if (!this.ident.eh_FECHA_PARENTESES(tk)) {
			lancarErro(tk, "<t_fecha_par>");
			return false;
		}
		if (!this.ident.eh_PONTO_VIRGULA(tk)) {
			lancarErro(tk, "<t_pto_vgl>");
			return false;
		}

		if (!bloco()) {
			lancarErro(tk, "<bloco>");
			return false;
		}

		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <lista_subprograma> ::= <subprograma_> ;
	 * <lista_subprograma> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean listaSubprograma() {

		if (subprograma()) {

			if (!this.ident.eh_PONTO_VIRGULA(tk)) {
				lancarErro(tk, "<t_pto_vgl>");
				return false;
			}

			if (!listaSubprograma()) {
				lancarErro(tk, "<lista_subprograma>");
				return false;
			}

			return true;
		}
		return this.ident.eh_LAMBDA();

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <secao_subprograma> ::= SUBPROGRAMS
	 * <lista_subprograma> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean secaoSuprograma() {

		if (this.ident.eh_SUBPROGRAMS(tk)) {
			return listaSubprograma();
		}
		return this.ident.eh_LAMBDA();
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <bloco_> ::= BEGIN <comandos_> END
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean bloco() {

		if (!this.ident.eh_BEGIN(tk)) {
			return false;
		}

		if (!comandos()) {
			lancarErro(tk, "<comandos>");
			return false;
		}

		if (!this.ident.eh_END(tk)) {
			lancarErro(tk, "<t_end>");
			return false;
		}

		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <comandos_> ::= <comando_> ;
	 * <comandos_> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean comandos() {

		if (comando()) {
			if (!this.ident.eh_PONTO_VIRGULA(tk)) {
				lancarErro(tk, "<t_pto_vgl>");
				return false;
			}

			if (!comandos()) {
				lancarErro(tk, "<comandos>");
				return false;
			}

			return true;
		}
		return this.ident.eh_LAMBDA();

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <comando_identificador> ::= :=
	 * <expressao_> | <chamada_metodo>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean comandoIdentificador() {

		Token tkPenultimo = this.tkPenultimo;
		Token identificador = null;

		if (this.ident.eh_ABRE_COLCHETES(tk)) {

//			if (!this.ident.eh_NUM(tk)) {
//				lancarErro(tk, "<t_numero>");
//				return false;
//			}
			
			if (tk != null) {
				identificador = this.tk.clone();

				identificador.setNome("t_integer");
				identificador.setLexema(Tipo.T_INTEIRO);
				identificador.setPalavraReservada(true);
				this.aSemantico.setAtributo(identificador, identificador.getLinha(), identificador
						.getColuna());

			}
			
			this.aSemantico.novoGeradorDeArvore();
			
			if (!expressao(this.ident.getPrimeirosDe(";"))) {
				lancarErro(tk, "<expressao>");
				return false;
			}
			
			this.aSemantico.resolverUltimoGeradorDeArvores();
			this.aSemantico.verificaCompatibilidade();
			
			this.aSemantico.setAtributo(tkPenultimo, tkPenultimo.getLinha(), tkPenultimo
					.getColuna());
			
			if (!this.ident.eh_FECHA_COLCHETES(tk)) {
				lancarErro(tk, "<t_fecha_colc>");
				return false;
			}

			if (!this.ident.eh_ATRIBUICAO(tk)) {
				lancarErro(tk, "<t_atrib>");
				return false;
			}

//			this.aSemantico.setAtributo(tkPenultimo, this.tkPenultimo.getLinha(), this.tkPenultimo
//					.getColuna());

			// XXX Fazer com que os tokens de sincronização passador a expressão
			// venham a partir de parâmetro do método
			this.aSemantico.novoGeradorDeArvore();
			if (!expressao(this.ident.getPrimeirosDe(";"))) {
				lancarErro(tk, "<expressao>");
				return false;
			}
			this.aSemantico.resolverUltimoGeradorDeArvores();

			this.aSemantico.verificaCompatibilidade(true);

			return true;
		}

		if (this.ident.eh_ATRIBUICAO(tk)) {

			if (this.identificadorAtribuicaoEhArray) {
//				addLexemaEsperado("[");
//				lancarErro(this.tkPenultimo, "<t_abre_colc>");
//				return false;
				throw new AnalisadorSemanticoUnknownRuntimeException("Erro! Ausência do índice de acesso do array");
			}

			this.aSemantico.setAtributo(tkPenultimo, this.tkPenultimo.getLinha(), this.tkPenultimo
					.getColuna());

			// XXX Fazer com que os tokens de sincronização passador a expressão
			// venham a partir de parâmetro do método
			this.aSemantico.novoGeradorDeArvore();
			if (!expressao(this.ident.getPrimeirosDe(";"))) {
				lancarErro(tk, "<expressao>");
				return false;
			}
			this.aSemantico.resolverUltimoGeradorDeArvores();

			this.aSemantico.verificaCompatibilidade(true);

			return true;
		}

		if (this.ident.eh_ABRE_PARENTESES(tk)) {

			if (!this.ident.eh_FECHA_PARENTESES(tk)) {
				lancarErro(tk, "<t_fecha_par>");
				return false;
			}

			return true;
		}

		return false;

	}

	protected boolean array() {

		Token identificador = this.tkPenultimo;
		if (this.ident.eh_ABRE_COLCHETES(tk)) {

			if (!this.ident.eh_NUM(tk)) {
				lancarErro(tk, "<t_numero>");
				return false;
			}

			Atributo at = this.aSemantico.getAtributo(identificador);
			if (at != null) {
				at.getTipo().setIndexArray(Integer.parseInt(this.tkPenultimo.getLexema()));
			}

			if (!this.ident.eh_FECHA_COLCHETES(tk)) {
				lancarErro(tk, "<t_fecha_colc>");
				return false;
			}

			return true;
		}

		return this.ident.eh_LAMBDA();
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <comando_> ::= INDENTIFICADOR
	 * <comando_identificador> | WRITE ( <escrita_> ) | READLN ( IDENTIFICADOR ) | IF (
	 * <expressao_relacional> ) THEN <bloco_> <comando_else>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean comando() {

		Token identificador = null;

		if (this.ident.eh_WRITE(tk)) {
			if (!this.ident.eh_ABRE_PARENTESES(tk)) {
				lancarErro(tk, "<t_abre_par>");
				return false;
			}

			if (!escrita()) {
				lancarErro(tk, "<escrita>");
				return false;
			}

			if (!this.ident.eh_FECHA_PARENTESES(tk)) {
				lancarErro(tk, "<t_fecha_par>");
				return false;
			}

			return true;

		}

		if (this.ident.eh_READLN(tk)) {
			if (!this.ident.eh_ABRE_PARENTESES(tk)) {
				lancarErro(tk, "<t_abre_par>");
				return false;
			}

			Token tkAtual = tk;

			if (!this.ident.eh_IDENTIFICADOR(tk)) {
				lancarErro(tk, "<t_ident>");
				return false;
			}

			if (!this.aSemantico.existeAtributo(tkAtual)) {
				throw new AnalisadorSemanticoUnknownRuntimeException(tkAtual);
			}

			if (!array()) {
				lancarErro(tk, "<array>");
				return false;
			}

			if (!this.ident.eh_FECHA_PARENTESES(tk)) {
				lancarErro(tk, "<t_fecha_par>");
				return false;
			}

			return true;

		}

		if (this.ident.eh_IF(tk)) {

			if (tk != null) {
				identificador = this.tk.clone();

				identificador.setNome("t_boolean");
				identificador.setLexema(Tipo.T_BOOLEANO);
				identificador.setPalavraReservada(true);
				this.aSemantico.setAtributo(identificador, identificador.getLinha(), identificador
						.getColuna());

			}
			if (!this.ident.eh_ABRE_PARENTESES(tk)) {
				lancarErro(tk, "<t_abre_par>");
				return false;
			}

			// XXX Fazer a união entre os tokensDeSincronização passados como
			// parâmetros e o(s) próximo(s) esperado(s) depois de expressão.
			this.aSemantico.novoGeradorDeArvore();
			if (!expressao(this.ident.getPrimeirosDe(")"))) {
				lancarErro(tk, "<expressao>");
				return false;
			}

			if (!this.ident.eh_FECHA_PARENTESES(tk)) {
				lancarErro(tk, "<t_fecha_par>");
				return false;
			}

			this.aSemantico.resolverUltimoGeradorDeArvores();
			this.aSemantico.verificaCompatibilidade();

			geradorDeCodigo.AG4_cond();

			if (!this.ident.eh_THEN(tk)) {
				lancarErro(tk, "<t_then>");
				return false;
			}

			if (!bloco()) {
				lancarErro(tk, "<bloco>");
				return false;
			}

			geradorDeCodigo.AG5_cond();

			if (!comandoElse()) {
				lancarErro(tk, "<comando_else>");
				return false;
			}

			geradorDeCodigo.AG6_cond();
			geradorDeCodigo.atualizaCodigo();
			return true;
		}

		if (this.ident.eh_WHILE(tk)) {

			if (tk != null) {

				identificador = this.tk.clone();

				identificador.setNome("t_boolean");
				identificador.setLexema(Tipo.T_BOOLEANO);
				identificador.setPalavraReservada(true);
				this.aSemantico.setAtributo(identificador, identificador.getLinha(), identificador
						.getColuna());

			}

			geradorDeCodigo.AG7_while();

			if (!this.ident.eh_ABRE_PARENTESES(tk)) {
				lancarErro(tk, "<t_abre_par>");
				return false;
			}

			// XXX Fazer a união entre os tokensDeSincronização passados como
			// parâmetros e o(s) próximo(s) esperado(s) depois de expressão.
			this.aSemantico.novoGeradorDeArvore();
			if (!expressao(this.ident.getPrimeirosDe(")"))) {
				lancarErro(tk, "<expressao>");
				return false;
			}
			this.aSemantico.resolverUltimoGeradorDeArvores();

			this.aSemantico.verificaCompatibilidade();

			geradorDeCodigo.AG4_cond();

			if (!this.ident.eh_FECHA_PARENTESES(tk)) {
				lancarErro(tk, "<t_fecha_par>");
				return false;
			}

			if (!this.ident.eh_DO(tk)) {
				lancarErro(tk, "<t_do>");
				return false;
			}

			if (!bloco()) {
				lancarErro(tk, "<bloco>");
				return false;
			}
			geradorDeCodigo.AG8_while();
			geradorDeCodigo.atualizaCodigo();
			return true;
		}

		if (tk != null) {
			identificador = this.tk.clone();
		}

		if (this.ident.eh_IDENTIFICADOR(tk)) {

			if (!this.aSemantico.existeAtributo(identificador)) {
				throw new AnalisadorSemanticoUnknownRuntimeException(identificador);
			}

			this.identificadorAtribuicaoEhArray = this.aSemantico.getAtributo(identificador)
					.getTipo().ehArray();

			geradorDeCodigo.pushLexema(identificador.getLexema());
			geradorDeCodigo.AG1_exp();
			if (!comandoIdentificador()) {
				lancarErro(tk, "<comando_identificador>");
				return false;
			}

			geradorDeCodigo.AG2_exp();
			return true;
		}

		return false;
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <escrita_> ::= IDENTIFICADOR | ALFA | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean escrita() {
		Token tkAtual = tk;
		if (this.ident.eh_IDENTIFICADOR(tk)) {

			if (!this.aSemantico.existeAtributo(tkAtual)) {
				throw new AnalisadorSemanticoUnknownRuntimeException(tkAtual);
			}

			if (!array()) {
				lancarErro(tk, "<array>");
				return false;
			}

			return true;
		}

		return this.ident.eh_ALFA(tk) || this.ident.eh_NUM(tk) || true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <comando_else> ::= ELSE <bloco_> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean comandoElse() {
		if (this.ident.eh_ELSE(tk)) {
			if (!bloco()) {
				lancarErro(tk, "<bloco>");
				return false;
			}
			return true;
		}
		return this.ident.eh_LAMBDA();
	}

	public static final int NAO_ACHOU = -1;
	public static final int ACHOU_PRIMEIRO = 0;
	public static final int ACHOU_TOKEN_DE_SINCRONIZACAO = 1;

	protected int TESTE(List<String> primeiros, List<String> tokensDeSincronizacao,
			String simboloTerminalOuNaoTerminalEsperado) {
		if (primeiros.contains(""))
			return ACHOU_PRIMEIRO;// Produção Vazia

		if (this.tk != null
				&& (primeiros.contains(this.tk.getLexema()) || (!this.tk.ehPalavraReservada() && primeiros
						.contains(this.tk.getTipo())))) {
			return ACHOU_PRIMEIRO;// O Token atual já é o esperado
		}

		// if (tokensDeSincronizacao.contains(""))
		// return ACHOU_TOKEN_DE_SINCRONIZACAO;// Produção Vazia

		// Reportar erro!
		addErro(this.tk, simboloTerminalOuNaoTerminalEsperado, primeiros);

		// "Entrar em pânico"
		while (true) {
			if (this.tk == null) {
				return NAO_ACHOU;// Esvaziei a entrada e não encontrei o que
				// queria :(
			}

			if (primeiros.contains(this.tk.getLexema())
					|| (!this.tk.ehPalavraReservada() && primeiros.contains(this.tk.getTipo()))) {
				return ACHOU_PRIMEIRO;
			}

			if (tokensDeSincronizacao.contains(this.tk.getLexema())
					|| (!this.tk.ehPalavraReservada() && tokensDeSincronizacao.contains(this.tk
							.getTipo()))) {
				return ACHOU_TOKEN_DE_SINCRONIZACAO;
			}

			obtemToken();
		}
	}

	private boolean contem(Collection<?> colecao, Token tk) {
		return tk != null && (colecao.contains(tk.getLexema()) || colecao.contains(tk.getTipo()));
	}

	private List<String> uniao(List<String> c1, List<String> c2) {
		List<String> lista = new ArrayList<String>(c1.size() + c2.size());
		lista.addAll(c1);
		lista.addAll(c2);
		return lista;
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <expressao_> ::= <expressao_simples>
	 * <expressao_relacional>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean expressao(List<String> tokensDeSincronizacao) {
		int r;

		List<String> primeirosDeExpressao = this.ident.getPrimeirosDeExpressao();
		r = TESTE(primeirosDeExpressao, tokensDeSincronizacao, "<expressao>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		// this.aSemantico.novoGeradorDeArvore();

		List<String> primeirosDeExpressaoRelacional = this.ident
				.getPrimeirosDeExpressaoRelacional();
		if (!expressaoSimples(uniao(tokensDeSincronizacao, primeirosDeExpressaoRelacional))) {
			lancarErro(tk, "<expressao_simples>");
			return false;
		}
		if (contem(tokensDeSincronizacao, this.tk)
				&& !contem(primeirosDeExpressaoRelacional, this.tk)) {
			return true;// Achou token de sinc. de algum ñ-terminal acima.
		}
		if (!expressaoRelacional(tokensDeSincronizacao)) {
			lancarErro(tk, "<expressao_relacional>");
			return false;
		}
		// this.aSemantico.resolverUltimoGeradorDeArvores();
		if (contem(tokensDeSincronizacao, this.tk))
			return true;// Achou token de sinc. de algum ñ-terminal acima.

		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <expressao_relacional> ::=
	 * <operador_relacional> <expressao_simples> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean expressaoRelacional(List<String> tokensDeSincronizacao) {

		int r;

		List<String> primeirosDeExpressaoRelacional = this.ident
				.getPrimeirosDeExpressaoRelacional();
		r = TESTE(primeirosDeExpressaoRelacional, tokensDeSincronizacao, "<expressao_relacional>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		Token tkAtual = tk;
		if (this.ident.eh_IGUAL(tk) || this.ident.eh_DIFERENTE(tk) || this.ident.eh_MAIOR(tk)
				|| this.ident.eh_MENOR(tk) || this.ident.eh_MAIOR_IGUAL(tk)
				|| this.ident.eh_MENOR_IGUAL(tk)) {

			this.aSemantico.addNodo(tkAtual);

			if (!expressaoSimples(tokensDeSincronizacao)) {
				lancarErro(tk, "<expressao_simples>");
				return false;
			}

			if (contem(tokensDeSincronizacao, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			// this.aSemantico.verificaEhRelacionalFim();

			return true;

		}

		return this.ident.eh_LAMBDA();
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <expressao_simples> ::= <termo_>
	 * <expressao_aditiva>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean expressaoSimples(List<String> tokensDeSincronizacao) {

		int r;

		List<String> primeirosDeTermo = this.ident.getPrimeirosDeTermo();
		r = TESTE(primeirosDeTermo, tokensDeSincronizacao, "<expressao_simples>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		List<String> primeirosDeExpressaoAditiva = this.ident.getPrimeirosDeExpressaoAditiva();

		if (!termo(uniao(tokensDeSincronizacao, primeirosDeExpressaoAditiva))) {
			lancarErro(tk, "<termo>");
			return false;
		}

		if (contem(tokensDeSincronizacao, this.tk) && !contem(primeirosDeExpressaoAditiva, this.tk))
			return true;// Achou token de sinc. de algum ñ-terminal acima.

		if (!expressaoAditiva(tokensDeSincronizacao)) {
			lancarErro(tk, "<expressao_aditiva>");
			return false;
		}

		if (contem(tokensDeSincronizacao, this.tk))
			return true;// Achou token de sinc. de algum ñ-terminal acima.

		return true;

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <expressao_aditiva> ::=
	 * <operador_aditivo> <termo_> <expressao_aditiva> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean expressaoAditiva(List<String> tokensDeSincronizacao) {

		int r;

		List<String> primeirosDeExpressaoAditiva = this.ident.getPrimeirosDeExpressaoAditiva();
		r = TESTE(primeirosDeExpressaoAditiva, tokensDeSincronizacao, "<expressao_aditiva>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		Token tkAtual = tk;
		// boolean eh_MAIS = false;
		// boolean eh_OR = false;
		if (this.ident.eh_MAIS(tk) || this.ident.eh_MENOS(tk) || this.ident.eh_OR(tk)) {

			// if (eh_MAIS) {
			// this.aSemantico.verificaOperadorSoma();
			// } else if (eh_OR) {
			// this.aSemantico.verificaEhBoolean();
			// } else {
			// this.aSemantico.verificaEhInteger();
			// }

			this.aSemantico.addNodo(tkAtual);

			if (!termo(uniao(tokensDeSincronizacao, primeirosDeExpressaoAditiva))) {
				lancarErro(tk, "<termo>");
				return false;
			}

			if (contem(tokensDeSincronizacao, this.tk)
					&& !contem(primeirosDeExpressaoAditiva, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			if (!expressaoAditiva(tokensDeSincronizacao)) {
				lancarErro(tk, "<expressao_aditiva>");
				return false;
			}

			if (contem(tokensDeSincronizacao, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			return true;
		}

		return this.ident.eh_LAMBDA();
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <termo_> ::= <expressao_unaria>
	 * <expressao_multiplicativa>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean termo(List<String> tokensDeSincronizacao) {

		int r;

		List<String> primeirosDeExpressaoUnaria = this.ident.getPrimeirosDeExpressaoUnaria();
		r = TESTE(primeirosDeExpressaoUnaria, tokensDeSincronizacao, "<termo>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		List<String> primeirosDeExpressaoMultiplicativa = this.ident
				.getPrimeirosDeExpressaoMultiplicativa();

		if (!expressaoUnaria(uniao(tokensDeSincronizacao, primeirosDeExpressaoMultiplicativa))) {
			lancarErro(tk, "<expressao_unaria>");
			return false;
		}

		if (contem(tokensDeSincronizacao, this.tk)
				&& !contem(primeirosDeExpressaoMultiplicativa, this.tk))
			return true;// Achou token de sinc. de algum ñ-terminal acima.

		if (!expressaoMultiplicativa(tokensDeSincronizacao)) {
			lancarErro(tk, "<expressao_multiplicativa>");
			return false;
		}

		if (contem(tokensDeSincronizacao, this.tk))
			return true;// Achou token de sinc. de algum ñ-terminal acima.

		return true;
	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <expressao_ multiplicativa> ::=
	 * <operador_multiplicativo> <expressao_unaria> <expressao_multiplicativa> | î
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean expressaoMultiplicativa(List<String> tokensDeSincronizacao) {

		int r;

		List<String> primeirosDeExpressaoMultiplicativa = this.ident
				.getPrimeirosDeExpressaoMultiplicativa();
		r = TESTE(primeirosDeExpressaoMultiplicativa, tokensDeSincronizacao,
				"<expressao_multiplicativa>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		Token tkAtual = tk;
		// boolean eh_AND = false;
		if (this.ident.eh_MULTIPLICACAO(tk) || this.ident.eh_DIVISAO(tk) || this.ident.eh_DIV(tk)
				|| this.ident.eh_MOD(tk) || this.ident.eh_AND(tk)) {

			// if (eh_AND) {
			// this.aSemantico.verificaEhBoolean();
			// } else {
			// this.aSemantico.verificaEhInteger();
			// }

			this.aSemantico.addNodo(tkAtual);

			if (!expressaoUnaria(uniao(tokensDeSincronizacao, primeirosDeExpressaoMultiplicativa))) {
				lancarErro(tk, "<expressao_unaria>");
				return false;
			}

			if (contem(tokensDeSincronizacao, this.tk)
					&& !contem(primeirosDeExpressaoMultiplicativa, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			if (!expressaoMultiplicativa(tokensDeSincronizacao)) {
				lancarErro(tk, "<expressao_multiplicativa>");
				return false;
			}

			if (contem(tokensDeSincronizacao, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			return true;
		}

		return this.ident.eh_LAMBDA();

	}

	/**
	 * Implementa a seguinte regra de produção da gramática: <expressao_unaria> ::=
	 * <operador_unario> <expressao_unaria> | <fator_>
	 * 
	 * @return true caso reconheca os tokens e false caso contrário
	 */
	protected boolean expressaoUnaria(List<String> tokensDeSincronizacao) {

		int r;

		List<String> primeirosDeExpressaoUnaria = this.ident.getPrimeirosDeExpressaoUnaria();
		r = TESTE(primeirosDeExpressaoUnaria, tokensDeSincronizacao, "<expressao_unaria>");
		if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
			return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
		}

		Token tkAtual = tk;
		// boolean eh_NOT = false;
		if (this.ident.eh_NOT(tk) || this.ident.eh_MENOS(tk)) {

			// TODO Considerar operador unario no a. semântico
			// this.aSemantico.addNodo(tkAtual);

			if (!expressaoUnaria(tokensDeSincronizacao)) {
				lancarErro(tk, "<expressao_unaria>");
				return false;
			}

			// if (eh_NOT) {
			// this.aSemantico.verificaEhBoolean();
			// } else {
			// this.aSemantico.verificaEhInteger();
			// }

			if (contem(tokensDeSincronizacao, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			return true;
		}

		if (this.ident.eh_ABRE_PARENTESES(tk)) {
			this.aSemantico.novoGeradorDeArvore();

			List<String> primeirosDeFechaParenteses = this.ident.getPrimeirosDe(")");

			if (!expressao(uniao(tokensDeSincronizacao, primeirosDeFechaParenteses))) {
				lancarErro(tk, "<t_abre_par>");
				return false;
			}

			if (contem(tokensDeSincronizacao, this.tk)
					&& !contem(primeirosDeFechaParenteses, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			primeirosDeFechaParenteses = this.ident.getPrimeirosDe(")");
			r = TESTE(primeirosDeFechaParenteses, tokensDeSincronizacao, ")");
			if (r == ACHOU_TOKEN_DE_SINCRONIZACAO || r == NAO_ACHOU) {
				return r == ACHOU_TOKEN_DE_SINCRONIZACAO;
			}

			if (!this.ident.eh_FECHA_PARENTESES(tk)) {
				lancarErro(tk, "<t_fecha_par>");
				return false;
			}
			this.aSemantico.resolverUltimoGeradorDeArvores();

			// this.aSemantico.verificaCompatibilidade();

			if (contem(tokensDeSincronizacao, this.tk))
				return true;// Achou token de sinc. de algum ñ-terminal acima.

			return true;

		}

		if (this.ident.eh_IDENTIFICADOR(tk)) {

			if (!this.aSemantico.existeAtributo(tkAtual)) {
				throw new AnalisadorSemanticoUnknownRuntimeException(tkAtual);
			} else {
				Nodo nodo = new Nodo(this.aSemantico.getAtributo(tkAtual));
				this.aSemantico.addNodo(nodo);
			}

			// TODO TRATAR ERRO!
			if (!array()) {
				lancarErro(tk, "<array>");
				return false;
			}
			return true;
		}

		if (!this.ident.eh_NUM(tk) && !this.ident.eh_FALSE(tk) && !this.ident.eh_TRUE(tk)
				&& !this.ident.eh_ALFA(tk)) {
			return false;
		} else {
			this.aSemantico.addNodo(tkAtual);
		}

		return true;
	}

	public Token getTokenAtual() {
		return tk;
	}

	public List<AnalisadorSintaticoUnknownRuntimeException> getListaDeErros() {
		return listaDeErros;
	}

	public AnalisadorSemantico getASemantico() {
		return aSemantico;
	}

	public List<String> getCodigoIntermediario() {
		return this.geradorDeCodigo.getCodigos();
	}

}
